package com.google.vrtoolkit.cardboard;

import android.content.*;
import android.opengl.Matrix;
import android.view.*;
import android.util.*;
import java.nio.*;
import android.opengl.*;
import android.graphics.*;

class UiLayer {
    private static final String TAG;
    private static final int NORMAL_COLOR = -3355444;
    private static final int PRESSED_COLOR = -12303292;
    private static final float CENTER_LINE_THICKNESS_DP = 4.0f;
    private static final int BUTTON_WIDTH_DP = 28;
    private static final float TOUCH_SLOP_FACTOR = 1.5f;
    private final int mTouchWidthPx;
    private volatile Rect mTouchRect;
    private boolean mDownWithinBounds;
    private Context mContext;
    private final GLStateBackup mGlStateBackup;
    private final ShaderProgram mShader;
    private final SettingsButtonRenderer mSettingsButtonRenderer;
    private final AlignmentMarkerRenderer mAlignmentMarkerRenderer;
    private Viewport mViewport;
    private boolean mShouldUpdateViewport;
    private boolean mSettingsButtonEnabled;
    private boolean mAlignmentMarkerEnabled;
    private boolean initialized;
    
    UiLayer(final Context context) {
        super();
        this.mTouchRect = new Rect();
        this.mShouldUpdateViewport = true;
        this.mSettingsButtonEnabled = true;
        this.mAlignmentMarkerEnabled = true;
        this.mContext = context;
        final float density = context.getResources().getDisplayMetrics().density;
        final int buttonWidthPx = (int)(28.0f * density);
        this.mTouchWidthPx = (int)(buttonWidthPx * 1.5f);
        this.mGlStateBackup = new GLStateBackup();
        this.mShader = new ShaderProgram();
        this.mSettingsButtonRenderer = new SettingsButtonRenderer(this.mShader, buttonWidthPx);
        this.mAlignmentMarkerRenderer = new AlignmentMarkerRenderer(this.mShader, this.mTouchWidthPx, 4.0f * density);
        this.mViewport = new Viewport();
    }
    
    void updateViewport(final Viewport viewport) {
        synchronized (this) {
            if (this.mViewport.equals(viewport)) {
                return;
            }
            final int w = viewport.width;
            final int h = viewport.height;
            this.mTouchRect = new Rect((w - this.mTouchWidthPx) / 2, h - this.mTouchWidthPx, (w + this.mTouchWidthPx) / 2, h);
            this.mViewport.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
            this.mShouldUpdateViewport = true;
        }
    }
    
    void initializeGl() {
        this.mShader.initializeGl();
        this.mGlStateBackup.clearTrackedVertexAttributes();
        this.mGlStateBackup.addTrackedVertexAttribute(this.mShader.aPosition);
        this.mGlStateBackup.readFromGL();
        this.mSettingsButtonRenderer.initializeGl();
        this.mAlignmentMarkerRenderer.initializeGl();
        this.mGlStateBackup.writeToGL();
        this.initialized = true;
    }
    
    void draw() {
        if (!this.getSettingsButtonEnabled() && !this.getAlignmentMarkerEnabled()) {
            return;
        }
        if (!this.initialized) {
            this.initializeGl();
        }
        this.mGlStateBackup.readFromGL();
        synchronized (this) {
            if (this.mShouldUpdateViewport) {
                this.mShouldUpdateViewport = false;
                this.mSettingsButtonRenderer.updateViewport(this.mViewport);
                this.mAlignmentMarkerRenderer.updateViewport(this.mViewport);
            }
            this.mViewport.setGLViewport();
        }
        if (this.getSettingsButtonEnabled()) {
            this.mSettingsButtonRenderer.draw();
        }
        if (this.getAlignmentMarkerEnabled()) {
            this.mAlignmentMarkerRenderer.draw();
        }
        this.mGlStateBackup.writeToGL();
    }
    
    synchronized void setAlignmentMarkerEnabled(final boolean enabled) {
        if (this.mAlignmentMarkerEnabled != enabled) {
            this.mAlignmentMarkerEnabled = enabled;
            this.mShouldUpdateViewport = true;
        }
    }
    
    synchronized boolean getAlignmentMarkerEnabled() {
        return this.mAlignmentMarkerEnabled;
    }
    
    synchronized void setSettingsButtonEnabled(final boolean enabled) {
        if (this.mSettingsButtonEnabled != enabled) {
            this.mSettingsButtonEnabled = enabled;
            this.mShouldUpdateViewport = true;
        }
    }
    
    synchronized boolean getSettingsButtonEnabled() {
        return this.mSettingsButtonEnabled;
    }
    
    boolean onTouchEvent(final MotionEvent e) {
        boolean touchWithinBounds = false;
        synchronized (this) {
            if (!this.mSettingsButtonEnabled) {
                return false;
            }
            touchWithinBounds = this.mTouchRect.contains((int)e.getX(), (int)e.getY());
        }
        if (e.getActionMasked() == 0 && touchWithinBounds) {
            this.mDownWithinBounds = true;
        }
        if (!this.mDownWithinBounds) {
            return false;
        }
        if (e.getActionMasked() == 1) {
            if (touchWithinBounds) {
                UiUtils.launchOrInstallCardboard(this.mContext);
            }
            this.mDownWithinBounds = false;
        }
        else if (e.getActionMasked() == 3) {
            this.mDownWithinBounds = false;
        }
        this.setPressed(this.mDownWithinBounds && touchWithinBounds);
        return true;
    }
    
    private void setPressed(final boolean pressed) {
        if (this.mSettingsButtonRenderer != null) {
            this.mSettingsButtonRenderer.setColor(pressed ? -12303292 : -3355444);
        }
    }
    
    private static void checkGlError(final String op) {
        final int error;
        if ((error = GLES20.glGetError()) != 0) {
            final String tag = UiLayer.TAG;
            final String value = String.valueOf(String.valueOf(op));
            Log.e(tag, new StringBuilder(21 + value.length()).append(value).append(": glError ").append(error).toString());
            final String value2 = String.valueOf(String.valueOf(op));
            throw new RuntimeException(new StringBuilder(21 + value2.length()).append(value2).append(": glError ").append(error).toString());
        }
    }
    
    private static float lerp(final float a, final float b, final float t) {
        return a * (1.0f - t) + b * t;
    }
    
    static {
        TAG = UiLayer.class.getSimpleName();
    }
    
    private static class ShaderProgram
    {
        private static final String VERTEX_SHADER = "uniform mat4 uMVPMatrix;\nattribute vec2 aPosition;\nvoid main() {\n    gl_Position = uMVPMatrix * vec4(aPosition, 0.0, 1.0);\n}\n";
        private static final String FRAGMENT_SHADER = "precision mediump float;\nuniform vec4 uColor;\nvoid main() {\n    gl_FragColor = uColor;\n}\n";
        public int program;
        public int aPosition;
        public int uMvpMatrix;
        public int uColor;
        
        void initializeGl() {
            this.program = this.createProgram("uniform mat4 uMVPMatrix;\nattribute vec2 aPosition;\nvoid main() {\n    gl_Position = uMVPMatrix * vec4(aPosition, 0.0, 1.0);\n}\n", "precision mediump float;\nuniform vec4 uColor;\nvoid main() {\n    gl_FragColor = uColor;\n}\n");
            if (this.program == 0) {
                throw new RuntimeException("Could not create program");
            }
            this.aPosition = GLES20.glGetAttribLocation(this.program, "aPosition");
            checkGlError("glGetAttribLocation aPosition");
            if (this.aPosition == -1) {
                throw new RuntimeException("Could not get attrib location for aPosition");
            }
            this.uMvpMatrix = GLES20.glGetUniformLocation(this.program, "uMVPMatrix");
            if (this.uMvpMatrix == -1) {
                throw new RuntimeException("Could not get uniform location for uMVPMatrix");
            }
            this.uColor = GLES20.glGetUniformLocation(this.program, "uColor");
            if (this.uColor == -1) {
                throw new RuntimeException("Could not get uniform location for uColor");
            }
        }
        
        private int loadShader(final int shaderType, final String source) {
            int shader = GLES20.glCreateShader(shaderType);
            if (shader != 0) {
                GLES20.glShaderSource(shader, source);
                GLES20.glCompileShader(shader);
                final int[] compiled = { 0 };
                GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
                if (compiled[0] == 0) {
                    Log.e(UiLayer.TAG, new StringBuilder(37).append("Could not compile shader ").append(shaderType).append(":").toString());
                    Log.e(UiLayer.TAG, GLES20.glGetShaderInfoLog(shader));
                    GLES20.glDeleteShader(shader);
                    shader = 0;
                }
            }
            return shader;
        }
        
        private int createProgram(final String vertexSource, final String fragmentSource) {
            final int vertexShader = this.loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
            if (vertexShader == 0) {
                return 0;
            }
            final int pixelShader = this.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
            if (pixelShader == 0) {
                return 0;
            }
            int program = GLES20.glCreateProgram();
            if (program != 0) {
                GLES20.glAttachShader(program, vertexShader);
                checkGlError("glAttachShader");
                GLES20.glAttachShader(program, pixelShader);
                checkGlError("glAttachShader");
                GLES20.glLinkProgram(program);
                final int[] linkStatus = { 0 };
                GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
                if (linkStatus[0] != 1) {
                    Log.e(UiLayer.TAG, "Could not link program: ");
                    Log.e(UiLayer.TAG, GLES20.glGetProgramInfoLog(program));
                    GLES20.glDeleteProgram(program);
                    program = 0;
                }
                checkGlError("glLinkProgram");
            }
            return program;
        }
    }
    
    private static class MeshRenderer
    {
        private static final int BYTES_PER_FLOAT = 4;
        private static final int BYTES_PER_SHORT = 4;
        protected static final int COMPONENTS_PER_VERT = 2;
        private static final int DATA_STRIDE_BYTES = 8;
        private static final int DATA_POS_OFFSET = 0;
        protected int mArrayBufferId;
        protected int mElementBufferId;
        protected ShaderProgram mShader;
        protected float[] mMvp;
        private int mNumIndices;
        
        MeshRenderer(final ShaderProgram shader) {
            super();
            this.mArrayBufferId = -1;
            this.mElementBufferId = -1;
            this.mMvp = new float[16];
            this.mShader = shader;
        }
        
        void genAndBindBuffers(final float[] vertexData, final short[] indexData) {
            final FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
            vertexBuffer.put(vertexData).position(0);
            this.mNumIndices = indexData.length;
            final ShortBuffer indexBuffer = ByteBuffer.allocateDirect(this.mNumIndices * 4).order(ByteOrder.nativeOrder()).asShortBuffer();
            indexBuffer.put(indexData).position(0);
            final int[] bufferIds = new int[2];
            GLES20.glGenBuffers(2, bufferIds, 0);
            this.mArrayBufferId = bufferIds[0];
            this.mElementBufferId = bufferIds[1];
            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, this.mArrayBufferId);
            GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, (Buffer)vertexBuffer, GLES20.GL_STATIC_DRAW);
            GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, this.mElementBufferId);
            GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexData.length * 4, (Buffer)indexBuffer, GLES20.GL_STATIC_DRAW);
            checkGlError("genAndBindBuffers");
        }
        
        void updateViewport(final Viewport viewport) {
            Matrix.setIdentityM(this.mMvp, 0);
        }
        
        void draw() {
            GLES20.glDisable(GLES20.GL_DEPTH_TEST);
            GLES20.glDisable(GLES20.GL_CULL_FACE);
            GLES20.glUseProgram(this.mShader.program);
            GLES20.glUniformMatrix4fv(this.mShader.uMvpMatrix, 1, false, this.mMvp, 0);
            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, this.mArrayBufferId);
            GLES20.glVertexAttribPointer(this.mShader.aPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
            GLES20.glEnableVertexAttribArray(this.mShader.aPosition);
            GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, this.mElementBufferId);
            GLES20.glDrawElements(5, this.mNumIndices, GLES20.GL_UNSIGNED_SHORT, 0);
        }
    }
    
    private static class AlignmentMarkerRenderer extends MeshRenderer
    {
        private static final int COLOR;
        private float mVerticalBorderPaddingPx;
        private float mLineThicknessPx;
        
        AlignmentMarkerRenderer(final ShaderProgram shader, final float verticalBorderPaddingPx, final float lineThicknessPx) {
            super(shader);
            this.mVerticalBorderPaddingPx = verticalBorderPaddingPx;
            this.mLineThicknessPx = lineThicknessPx;
        }
        
        void initializeGl() {
            final float[] vertexData = { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f };
            final short[] indexData = new short[vertexData.length / 2];
            for (int i = 0; i < indexData.length; ++i) {
                indexData[i] = (short)i;
            }
            this.genAndBindBuffers(vertexData, indexData);
        }
        
        @Override
        void updateViewport(final Viewport viewport) {
            Matrix.setIdentityM(this.mMvp, 0);
            final float xScale = this.mLineThicknessPx / viewport.width;
            final float yScale = 1.0f - 2.0f * this.mVerticalBorderPaddingPx / viewport.height;
            Matrix.scaleM(this.mMvp, 0, xScale, yScale, 1.0f);
        }
        
        @Override
        void draw() {
            GLES20.glUseProgram(this.mShader.program);
            GLES20.glUniform4f(this.mShader.uColor, Color.red(AlignmentMarkerRenderer.COLOR) / 255.0f, Color.green(AlignmentMarkerRenderer.COLOR) / 255.0f, Color.blue(AlignmentMarkerRenderer.COLOR) / 255.0f, Color.alpha(AlignmentMarkerRenderer.COLOR) / 255.0f);
            super.draw();
        }
        
        static {
            COLOR = Color.argb(255, 50, 50, 50);
        }
    }
    
    private static class SettingsButtonRenderer extends MeshRenderer
    {
        private static final int DEGREES_PER_GEAR_SECTION = 60;
        private static final int OUTER_RIM_END_DEG = 12;
        private static final int INNER_RIM_BEGIN_DEG = 20;
        private static final float OUTER_RADIUS = 1.0f;
        private static final float MIDDLE_RADIUS = 0.75f;
        private static final float INNER_RADIUS = 0.3125f;
        private static final int NUM_VERTICES = 60;
        private int mButtonWidthPx;
        private int mColor;
        
        SettingsButtonRenderer(final ShaderProgram shader, final int buttonWidthPx) {
            super(shader);
            this.mColor = -3355444;
            this.mButtonWidthPx = buttonWidthPx;
        }
        
        void initializeGl() {
            final float[] vertexData = new float[120];
            final int numVerticesPerRim = 30;
            final float lerpInterval = 8.0f;
            for (int i = 0; i < numVerticesPerRim; ++i) {
                final float theta = i / numVerticesPerRim * 360.0f;
                final float mod = theta % 60.0f;
                float r;
                if (mod <= 12.0f) {
                    r = 1.0f;
                }
                else if (mod <= 20.0f) {
                    r = lerp(1.0f, 0.75f, (mod - 12.0f) / lerpInterval);
                }
                else if (mod <= 40.0f) {
                    r = 0.75f;
                }
                else if (mod <= 48.0f) {
                    r = lerp(0.75f, 1.0f, (mod - 60.0f + 20.0f) / lerpInterval);
                }
                else {
                    r = 1.0f;
                }
                vertexData[2 * i] = r * (float)Math.cos(Math.toRadians(90.0f - theta));
                vertexData[2 * i + 1] = r * (float)Math.sin(Math.toRadians(90.0f - theta));
            }
            final int innerStartingIndex = 2 * numVerticesPerRim;
            for (int j = 0; j < numVerticesPerRim; ++j) {
                final float theta2 = j / numVerticesPerRim * 360.0f;
                vertexData[innerStartingIndex + 2 * j] = 0.3125f * (float)Math.cos(Math.toRadians(90.0f - theta2));
                vertexData[innerStartingIndex + 2 * j + 1] = 0.3125f * (float)Math.sin(Math.toRadians(90.0f - theta2));
            }
            final short[] indexData = new short[62];
            for (int k = 0; k < numVerticesPerRim; ++k) {
                indexData[2 * k] = (short)k;
                indexData[2 * k + 1] = (short)(numVerticesPerRim + k);
            }
            indexData[indexData.length - 2] = 0;
            indexData[indexData.length - 1] = (short)numVerticesPerRim;
            this.genAndBindBuffers(vertexData, indexData);
        }
        
        synchronized void setColor(final int color) {
            this.mColor = color;
        }
        
        @Override
        void updateViewport(final Viewport viewport) {
            Matrix.setIdentityM(this.mMvp, 0);
            final float yScale = this.mButtonWidthPx / viewport.height;
            final float xScale = yScale * viewport.height / viewport.width;
            Matrix.translateM(this.mMvp, 0, 0.0f, yScale - 1.0f, 0.0f);
            Matrix.scaleM(this.mMvp, 0, xScale, yScale, 1.0f);
        }
        
        @Override
        void draw() {
            GLES20.glUseProgram(this.mShader.program);
            synchronized (this) {
                GLES20.glUniform4f(this.mShader.uColor, Color.red(this.mColor) / 255.0f, Color.green(this.mColor) / 255.0f, Color.blue(this.mColor) / 255.0f, Color.alpha(this.mColor) / 255.0f);
            }
            super.draw();
        }
    }
}